import { Helper } from "../core/helper";
import { uint_buf } from "../core/uint_buf";

class SM3_CTX {
  public a: number;
  public b: number;
  public c: number;
  public d: number;
  public e: number;
  public f: number;
  public g: number;
  public h: number;
  public get buf(): number[] {
    return this.buffer.Oo.Data;
  }
  public buffer: uint_buf;
}

const HASH_ROUND_NUM = 64;
const T: number[] = [0x0079CC4519, 0x007A879D8A];
export class SM3 {
  public static ROTL(x: number, shift: number) {
    shift %= 32;
    x = x >>> 0;
    return Helper.uint((x << shift) | (x >>> (32 - shift)));
  }
  public static FF(x: number, y: number, z: number, j: number) {
    if (j < 16) return x ^ y ^ z;
    else return (x & y) | (x & z) | (y & z);
  }
  public static GG(x: number, y: number, z: number, j: number) {
    if (j < 16) return x ^ y ^ z;
    else return (x & y) | (~x & z);
  }
  public static P0(x: number) {
    return x ^ SM3.ROTL(x, 9) ^ SM3.ROTL(x, 17);
  }
  public static P1(x: number) {
    return x ^ SM3.ROTL(x, 15) ^ SM3.ROTL(x, 23);
  }
  public static Init(): SM3_CTX {
    let ctx = new SM3_CTX();
    ctx.buffer = uint_buf.New(16);
    ctx.a = 0x7380166f;
    ctx.b = 0x4914b2b9;
    ctx.c = 0x172442d7;
    ctx.d = 0xda8a0600;
    ctx.e = 0xa96f30bc;
    ctx.f = 0x163138aa;
    ctx.g = 0xe38dee4d;
    ctx.h = 0xb0fb0e4e;

    return ctx;
  }
  public static ProcessBlock(ctx: SM3_CTX) {
    let j;
    let S1, S2, T1, T2;
    let a, b, c, d, e, f, g, h;
    let W = new Array(HASH_ROUND_NUM + 4);

    a = ctx.a;
    b = ctx.b;
    c = ctx.c;
    d = ctx.d;
    e = ctx.e;
    f = ctx.f;
    g = ctx.g;
    h = ctx.h;
    for (j = 0; j < HASH_ROUND_NUM + 4; j++) {
      if (j <= 0xf) W[j] = ctx.buf[j];
      else W[j] = SM3.P1(W[j - 16] ^ W[j - 9] ^ SM3.ROTL(W[j - 3], 15)) ^ SM3.ROTL(W[j - 13], 7) ^ W[j - 6];
    }

    for (j = 0; j < HASH_ROUND_NUM; j++) {
      S1 = SM3.ROTL(SM3.ROTL(a, 12) + e + SM3.ROTL(T[j < 16 ? 0 : 1], j), 7);
      S2 = S1 ^ SM3.ROTL(a, 12);
      T1 = SM3.FF(a, b, c, j) + d + S2 + (W[j] ^ W[j + 4]);
      T2 = SM3.GG(e, f, g, j) + h + S1 + W[j];
      d = c;
      c = SM3.ROTL(b, 9);
      b = a;
      a = T1;
      h = g;
      g = SM3.ROTL(f, 19);
      f = e;
      e = SM3.P0(T2);
    }

    ctx.a ^= a;
    ctx.b ^= b;
    ctx.c ^= c;
    ctx.d ^= d;
    ctx.e ^= e;
    ctx.f ^= f;
    ctx.g ^= g;
    ctx.h ^= h;
  }
  public static Update(ctx: SM3_CTX, data: number[], length: number) {
    for (let i of ctx.buffer.Push(data, 0, length, 0))
      if (i <= length) SM3.ProcessBlock(ctx);
  }
  public static Final(ctx: SM3_CTX) {
    if (ctx.buffer.IsFULL(0)) SM3.ProcessBlock(ctx);
    ctx.buffer.PushNext(0x80);
    if (ctx.buffer.IsFULL(2)) SM3.ProcessBlock(ctx);
    ctx.buffer.Fill(0, 2);
    ctx.buffer.PushTotal();
    SM3.ProcessBlock(ctx);

    let sha = new Array(32);
    let i = 0;

    Helper.CopyFrom4Reverse(sha, ctx.a, i++ << 2);
    Helper.CopyFrom4Reverse(sha, ctx.b, i++ << 2);
    Helper.CopyFrom4Reverse(sha, ctx.c, i++ << 2);
    Helper.CopyFrom4Reverse(sha, ctx.d, i++ << 2);
    Helper.CopyFrom4Reverse(sha, ctx.e, i++ << 2);
    Helper.CopyFrom4Reverse(sha, ctx.f, i++ << 2);
    Helper.CopyFrom4Reverse(sha, ctx.g, i++ << 2);
    Helper.CopyFrom4Reverse(sha, ctx.h, i++ << 2);
    return sha;
  }
  public static Make(data: number[]): string {
    var ctx = SM3.Init();
    SM3.Update(ctx, data, data.length);
    return Helper.ToHex(SM3.Final(ctx));
  }
}